//
//  NSDataAdditions.m
//  Tint
//
//  Created by Kevin Wu on 2/22/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "NSDataAdditions.h"
#import <CommonCrypto/CommonDigest.h>



@implementation NSData (Tint)


#pragma mark - Creating

- (NSString *)newASCIIEncodingString
{
    return [self newStringUsingEncoding:NSASCIIStringEncoding];
}

- (NSString *)newUTF8EncodingString
{
    return [self newStringUsingEncoding:NSUTF8StringEncoding];
}

- (NSString *)newStringUsingEncoding:(NSStringEncoding)encoding
{
    return [[NSString alloc] initWithData:self encoding:encoding];
}


#pragma mark - Hash

- (NSString *)newMD5HashString
{
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5([self bytes], [self length], result);
    
    NSString *fmt = @"%02x%02x%02x%02x%02x%02x%02x%02x"
                    @"%02x%02x%02x%02x%02x%02x%02x%02x";
    
    return [[NSString alloc] initWithFormat:fmt, 
            result[0], result[1], result[2], result[3], 
            result[4], result[5], result[6], result[7], 
            result[8], result[9], result[10], result[11], 
            result[12], result[13], result[14], result[15]];
}

- (NSString *)newSHA1HashString
{
    unsigned char result[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1([self bytes], [self length], result);
    
    NSString *fmt = @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
                    @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
    
    return [[NSString alloc] initWithFormat:fmt, 
            result[0], result[1], result[2], result[3], 
            result[4], result[5], result[6], result[7], 
            result[8], result[9], result[10], result[11], 
            result[12], result[13], result[14], result[15], 
            result[16], result[17], result[18], result[19]];
}



#pragma mark - Base64

+ (NSData *)newDataByDecodeBase64String:(NSString *)string
{
    if ( [string lengthOfBytesUsingEncoding:NSASCIIStringEncoding] < 1 ) {
        return nil;
    }
    
    static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
    static char *decodingTable = NULL;
    if ( decodingTable == NULL ) {
        decodingTable = malloc(256);
        if ( decodingTable == NULL ) {
            return nil;
        }
        memset(decodingTable, CHAR_MAX, 256);
        for ( NSUInteger i=0; i<64; i++ ) {
            decodingTable[(short)encodingTable[i]] = i;
        }
    }
    
    const char *characters = [string cStringUsingEncoding:NSASCIIStringEncoding];
    if ( characters == NULL ) {
        // Not an ASCII string!
        return nil;
    }
    
    char *bytes = malloc((([string lengthOfBytesUsingEncoding:NSASCIIStringEncoding] + 3) / 4) * 3);
    if ( bytes == NULL ) {
        return nil;
    }
    
    NSUInteger length = 0;
    
    NSUInteger i = 0;
    while ( YES ) {
        char buffer[4];
        short bufferLength;
        for ( bufferLength=0; bufferLength<4; i++ ) {
            
            if ( characters[i] == '\0' ) {
                break;
            }
            if (isspace(characters[i]) || 
                (characters[i] == '='))
            {
                continue;
            }
            
            buffer[bufferLength] = decodingTable[(short)characters[i]];
            if ( buffer[bufferLength++] == CHAR_MAX ) {
                // Illegal character!
                free(bytes);
                return nil;
            }
        }
        
        if ( bufferLength == 0 ) {
            break;
        }
        
        if ( bufferLength == 1 ) {
            // At least two characters are needed to produce one byte!
            free(bytes);
            return nil;
        }
        
        // Decode the characters in the buffer to bytes.
        bytes[length++] = (buffer[0] << 2) | (buffer[1] >> 4);
        if ( bufferLength > 2 ) {
            bytes[length++] = (buffer[1] << 4) | (buffer[2] >> 2);
        }
        if ( bufferLength > 3 ) {
            bytes[length++] = (buffer[2] << 6) | buffer[3];
        }
    }
    
    realloc(bytes, length);
    return [[NSData alloc] initWithBytesNoCopy:bytes length:length];
}

- (NSString *)newBase64EncodedString
{
    if ( [self length] < 1 ) {
        return nil;
    }
    
    static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
    char *characters = malloc((([self length] + 2) / 3) * 4);
    if ( characters == NULL ) {
        return nil;
    }
    
    NSUInteger length = 0;
    
    NSUInteger i = 0;
    while ( i < [self length] ) {
        char buffer[3] = { 0, 0, 0 };
        short bufferLength = 0;
        while ((bufferLength < 3) && 
               (i < [self length]))
        {
            buffer[bufferLength++] = ((char *)[self bytes])[i++];
        }
        
        // Encode the bytes in the buffer to four characters,
        // including padding "=" characters if necessary.
        characters[length++] = encodingTable[(buffer[0] & 0xFC) >> 2];
        characters[length++] = encodingTable[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xF0) >> 4)];
        if ( bufferLength > 1 ) {
            characters[length++] = encodingTable[((buffer[1] & 0x0F) << 2) | ((buffer[2] & 0xC0) >> 6)];
        } else {
            characters[length++] = '=';
        }
        if ( bufferLength > 2 ) {
            characters[length++] = encodingTable[buffer[2] & 0x3F];
        } else {
            characters[length++] = '=';
        }
    }
    
    return [[NSString alloc] initWithBytesNoCopy:characters 
                                          length:length 
                                        encoding:NSASCIIStringEncoding 
                                    freeWhenDone:YES];
}

@end
